use std::collections::BTreeMap;

use proc_macro::TokenStream;
use proc_macro2::Span;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput, Ident};

#[proc_macro_attribute]
pub fn io(attr: TokenStream, item: TokenStream) -> TokenStream {

    let input = parse_macro_input!(item as DeriveInput);
    let name = &input.ident;
    let attr_str = attr.to_string();

    let port = &attr_str[1..2];
    let pin = &attr_str[2..];
    let pin: u8 = pin.parse().unwrap();

    let port = format!("P{}", port);
    let port = Ident::new(&port, Span::call_site());

    let moder = Ident::new(&format!("moder{}", pin), Span::call_site());
    let ospeedr = Ident::new(&format!("ospeedr{}", pin), Span::call_site());
    let otyper = Ident::new(&format!("ot{}", pin), Span::call_site());
    let pupdr = Ident::new(&format!("pupdr{}", pin), Span::call_site());
    let br = Ident::new(&format!("br{}", pin), Span::call_site());
    let bs = Ident::new(&format!("bs{}", pin), Span::call_site());
    let idr = format_ident!("idr{}", pin);

    let afr_reg = if pin < 8 {
        format_ident!("afrl")
    } else {
        format_ident!("afrh")
    };
    let afr = format_ident!("{}{}", afr_reg, pin);

    let expanded = quote! {
        #input

        impl #name {
            #[inline(always)]
            pub fn read() -> bool {
                #port.idr.read().#idr().is_high()
            }

            #[inline(always)]
            pub fn set() {
                #port.bsrr.write(|w| {
                    w.#bs().set_bit()
                });
            }

            #[inline(always)]
            pub fn clr() {
                #port.bsrr.write(|w| {
                    w.#br().set_bit()
                });
            }

            #[inline(always)]
            pub fn set_output_pp(&self) {
                #port.moder.modify(|_, w| w.#moder().output());
                #port.ospeedr.modify(|_, w| w.#ospeedr().very_high_speed());
                #port.otyper.modify(|_, w| w.#otyper().push_pull());
                #port.pupdr.modify(|_, w| w.#pupdr().floating());
                #port.bsrr.write(|w| w.#br().set_bit());
            }

            #[inline(always)]
            pub fn set_input_up(&self) {
                #port.moder.modify(|_, w| w.#moder().input());
                #port.pupdr.modify(|_, w| w.#pupdr().pull_up());
            }
            
            #[inline(always)]
            pub fn set_input_down(&self) {
                #port.moder.modify(|_, w| w.#moder().input());
                #port.pupdr.modify(|_, w| w.#pupdr().pull_down());
            }

            #[inline(always)]
            pub fn set_input_analog(&self) {
                #port.moder.modify(|_, w| w.#moder().input());
                #port.pupdr.modify(|_, w| w.#pupdr().floating());
            }

            #[inline(always)]
            pub fn set_output_od(&self) {
                #port.moder.modify(|_, w| w.#moder().output());
                #port.ospeedr.modify(|_, w| w.#ospeedr().very_high_speed());
                #port.otyper.modify(|_, w| w.#otyper().open_drain());
                #port.pupdr.modify(|_, w| w.#pupdr().pull_up());
                #port.bsrr.write(|w| w.#bs().set_bit());
            }

            #[inline(always)]
            pub fn set_af(&self, af: u8) {
                #port.moder.modify(|_, w| w.#moder().alternate());
                #port.ospeedr.modify(|_, w| w.#ospeedr().very_high_speed());
                #port.otyper.modify(|_, w| w.#otyper().push_pull());
                #port.pupdr.modify(|_, w| w.#pupdr().floating());
                #port.#afr_reg.modify(|_, w| {
                    w.#afr().bits(af)
                } );
            }
            
        }
    };
    TokenStream::from(expanded)
}

#[proc_macro_derive(DerefPeriph)]
pub fn derive_deref_periph(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let pn = name.to_string().to_lowercase();
    let pn2 = name.to_string().to_uppercase();
    let pn = Ident::new(&pn, Span::call_site());
    let pn2 = Ident::new(&pn2, Span::call_site());
    let expanded = quote! {
        impl core::ops::Deref for #name {
            type Target = #pn::RegisterBlock;
            fn deref(&self) -> &Self::Target {
                unsafe { &*#pn2::PTR }
            }
        }
    };
    TokenStream::from(expanded)
}


#[proc_macro_derive(DerefGpio)]
pub fn derive_deref_gpio(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    let name_str = name.to_string();
    let port = name_str.as_bytes()[1];

    let port_name = format!("gpio{}", port.to_ascii_lowercase() as char);
    let port_name2 = format!("GPIO{}", port.to_ascii_uppercase() as char);

    let pn = Ident::new(&port_name, Span::call_site());
    let pn2 = Ident::new(&port_name2, Span::call_site());

    let expanded = quote! {

        impl core::ops::Deref for #name {
            type Target = #pn::RegisterBlock;

            fn deref(&self) -> &Self::Target {
                unsafe { &*#pn2::PTR }
            }
        }
    };
    TokenStream::from(expanded)
}

#[derive(Debug)]
enum OutputType {
    PP,
    OD,
}

#[derive(Debug)]
enum InputType {
    FLOAT,
    UP,
    DOWM,
}

#[derive(Debug)]
struct OutputMode {
    out: OutputType,
    input: InputType,
}

#[derive(Debug)]
struct AfMode {
    out: OutputType,
    input: InputType,
    af: u8,
}

#[derive(Debug)]
enum Mode {
    Out(OutputMode),
    In(InputType),
    Af(AfMode),
    Analog,
}

#[derive(Debug)]
struct Pin<'a> {
    port: &'a str,
    pin: u8,
}

fn parse_pin<'a>(name: &'a str) -> Pin {
    let port = &name[1..2];
    let pin = &name[2..];
    let pin: u8 = pin.parse().unwrap();
    Pin { port, pin }
}

fn parse_output(attrs: &[&str]) -> OutputMode {
    let out = match attrs[0] {
        "PP" => OutputType::PP,
        "OD" => OutputType::OD,
        _ => panic!("invalid out type"),
    };
    let input = match attrs[1] {
        "FLOAT" => InputType::FLOAT,
        "UP" => InputType::UP,
        "DOWN" => InputType::DOWM,
        _ => panic!("invalid in type"),
    };
    OutputMode { out, input }
}

fn parse_in(attrs: &[&str]) -> InputType {
    match attrs[0] {
        "FLOAT" => InputType::FLOAT,
        "UP" => InputType::UP,
        "DOWN" => InputType::DOWM,
        _ => panic!("invalid in:{}", attrs[0]),
    }
}

fn parse_af(attrs: &[&str]) -> AfMode {
    let out = match attrs[0] {
        "PP" => OutputType::PP,
        "OD" => OutputType::OD,
        _ => panic!("invalid out type"),
    };
    let input = match attrs[1] {
        "FLOAT" => InputType::FLOAT,
        "UP" => InputType::UP,
        "DOWN" => InputType::DOWM,
        _ => panic!("invalid in type:{}", attrs[1]),
    };
    let af: u8 = attrs[2].parse().unwrap();

    AfMode { out, input, af }
}

fn parse_mode(attrs: &[&str]) -> Mode {
    match attrs[0] {
        "OUT" => Mode::Out( parse_output(&attrs[1..]) ),
        "IN" => Mode::In( parse_in(&attrs[1..]) ),
        "AF" => Mode::Af( parse_af(&attrs[1..]) ),
        "ANALOG" => Mode::Analog,
        _ => panic!("invalid mode:{}", attrs[0]),
    }
}
#[derive(Debug)]
struct IoPin<'a> {
    name: &'a str,
    pin: Pin<'a>,
    mode: Mode,
}

fn parse<'a>(field: &'a str) -> IoPin<'a> {
    let attrs: Vec<&str> = field.trim().split("_").collect();
    let name = attrs[0];
    let pin = parse_pin(attrs[1]);
    let mode = parse_mode(&attrs[2..]);
    IoPin { name, pin, mode }
}

impl Mode {

    fn moder(&self) -> TokenStream2 {
        match self {
            Mode::Out(_) => quote! { output() },
            Mode::In(_) => quote! { input() },
            Mode::Analog => quote! { analog() },
            Mode::Af(_) => quote! { alternate() },
        }
    }

    fn pupdr(&self) -> TokenStream2 {
        match self {
            Mode::Out(OutputMode { out, input }) => {
                match input {
                    InputType::FLOAT => quote! { floating() },
                    InputType::DOWM => quote! { pull_down() },
                    InputType::UP => quote! { pull_up() },
                }
            },
            Mode::In(input) => {
                match input {
                    InputType::FLOAT => quote! { floating() },
                    InputType::DOWM => quote! { pull_down() },
                    InputType::UP => quote! { pull_up() },
                }
            },
            Mode::Af(AfMode { out, input, af }) => {
                match input {
                    InputType::FLOAT => quote! { floating() },
                    InputType::DOWM => quote! { pull_down() },
                    InputType::UP => quote! { pull_up() },
                }
            },
            _ => panic!("invalid pupdr")
        }
    }

    fn otyper(&self) -> TokenStream2 {
        match self {
            Mode::Af(AfMode { out, input, af }) => {
                match out {
                    OutputType::OD => quote! { open_drain() },
                    OutputType::PP => quote! { push_pull() },
                }
            }
            Mode::Out(out) => {
                match out.out {
                    OutputType::OD => quote! { open_drain() },
                    OutputType::PP => quote! { push_pull() },
                }
            },

            _ => panic!("invalid otype")
        }
    }
}

impl <'a> IoPin<'a>{

    fn moder(&self) -> TokenStream2 {
        let ident = format_ident!("moder{}", self.pin.pin);
        let mode = self.mode.moder();
        quote! {
            .#ident().#mode
        }
    }

    fn otyper(&self) -> TokenStream2 {
        let ident = format_ident!("ot{}", self.pin.pin);
        let otype = self.mode.otyper();
        quote! {
            .#ident().#otype
        }
    }

    fn pupdr(&self) -> TokenStream2 {
        let ident = format_ident!("pupdr{}", self.pin.pin);
        let pupdr = self.mode.pupdr();
        quote! {
            .#ident().#pupdr
        }
    }

    fn ospeedr(&self) -> TokenStream2 {
        let ident = format_ident!("ospeedr{}", self.pin.pin);
        match self.mode {
            Mode::Analog | Mode::In(_) => panic!("invalid ospeedr"),
            _ => quote! { .#ident().very_high_speed() }
        }
    }

    fn afr(&self) -> TokenStream2 {
        let ident = if self.pin.pin < 8 {
            format_ident!("afrl{}", self.pin.pin)
        } else {
            format_ident!("afrh{}", self.pin.pin)
        };
        let af = if let Mode::Af(AfMode { out, input, af }) = &self.mode {
            format_ident!("af{}", af)
        } else {
            panic!("invalid mode not af")
        };
        quote! {
            .#ident().#af()
        }
    }
}

#[proc_macro_attribute]
pub fn io_group(attr: TokenStream, input: TokenStream) -> TokenStream {

    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let attr = attr.to_string();
    println!("{}", attr);

    let attrs: Vec<&str> = attr.split("+").collect();
    let mut io_map: BTreeMap<&str, Vec<IoPin>> = BTreeMap::new();
    for attr in attrs {
        println!("parse:{}", attr);
        let pin = parse(attr);
        println!("{:#?}", pin);

        if let Some(vec) = io_map.get_mut(pin.pin.port) {
            vec.push(pin);
        } else {
            let mut vec = Vec::new();
            let name = pin.pin.port;
            vec.push(pin);
            io_map.insert(name, vec);
        }

    }

    let expand = quote! {
        #input

        impl #name {

        }
    };
    TokenStream::from(expand)
}
